Skip to content

LL userspace: enable cross-core DP pipelines - #11397

Open
kv2019i wants to merge 8 commits into
thesofproject:mainfrom
kv2019i:202611-lluser-cross-core-ipc-idc
Open

kv2019i wants to merge 8 commits into
thesofproject:mainfrom
kv2019i:202611-lluser-cross-core-ipc-idc

Conversation

@kv2019i

@kv2019i kv2019i commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

Initial implementation of cross-core pipeline support to full userspace builds. This covers the common use-case of running compressed codec module code in a different core (as DP module).

@kv2019i

kv2019i commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

FYI @ujfalusi @lgirdwood with this, compressed playback can be supported also with full userspace (both LL pipes and the DP codecs running in user-space).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical multicore userspace defects in state handling, locking, access grants, event signaling, and component dispatch can cause failed transitions, panics, unauthorized access, and deadlocks.

5 open issues
What changed in this PR

Adds initial cross-core pipeline support to full userspace builds, allowing DP modules such as compressed codecs to run on a different core from their pipeline.

Changes:

  • Routes component IDC requests through per-core userspace IPC threads.
  • Adds shared request storage, access grants, and userspace scheduler locking.
  • Enables cross-core streams while keeping LL modules on their pipeline’s core.
File Description
zephyr/​include/​rtos/​idc.h Declares userspace cross-core messaging interfaces.
src/​schedule/​zephyr_domain.c Excludes kernel-style scheduler blocking from userspace LL builds.
src/​ipc/​ipc4/​helper.c Allows remote DP modules and adjusts binding and teardown locks.
src/​ipc/​ipc-common.c Implements cross-core request transport, dispatch, and access grants.
src/​include/​sof/​ipc/​common.h Defines per-core cross-core request slots.
src/​include/​sof/​audio/​component_ext.h Allocates remote attribute results in user-accessible memory.
src/​idc/​zephyr_idc.c Routes userspace IDC sends through userspace IPC threads.
src/​idc/​idc.c Adds userspace component dispatch and explicit payload handling.
app/​ll_userspace_overlay.conf Removes the forced disabling of cross-core streams.

Review effort: Balanced

Comment thread src/kernel/idc/idc.c Resolved
Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc4/helper.c Resolved
Comment thread src/ipc/ipc4/helper.c Resolved
@intel-sofci

intel-sofci commented Nov 3, 2026 •

Copy link
Copy Markdown

PR 11397: test results

Run date: 2026-11-10 18:27 UTC

Tested commit: f63b6145f906b19bfa1a0593ae9aa6b51b49520a

mtl pass rate lnl pass rate ptl pass rate wcl pass rate nvl pass rate

@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from 72b5b67 to 0c12b46 Compare November 4, 2026 18:00
@kv2019i

kv2019i commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

V2 pushed:

  • addressing code review comments so far

@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from 0c12b46 to ba5a057 Compare November 5, 2026 09:28
@kv2019i

kv2019i commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

V3 pushed:

  • fixed the testbench build failure (undefined ARG_UNUSED())

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Two critical deadlock paths remain in synchronous cross-core IPC waits, where LL threads hold scheduler mutexes while awaiting IPC handlers that need those locks during BIND/UNBIND or remote DP startup.

6 open issues
1 resolved since last review

Review effort: Balanced

Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-common.c Resolved
@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from ba5a057 to 6d2890d Compare November 5, 2026 13:55
@kv2019i

kv2019i commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator Author

V4 pushed:

  • fixing review issues so far

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-helper.c Resolved
zephyr_ll_locks[] is in a user-accessible partition, so user threads
can read the lock handles for the lock syscalls. user_ll_grant_access()
runs in kernel context, but took the handle to grant from that same
array. A user thread could overwrite an entry and make the kernel grant
it access to an arbitrary kernel object. This is reachable whenever
grants are done while user threads already run, e.g. when a secondary
core and its IPC user thread are brought up, and with cross-core
streams also for the locks of other cores.

Keep a kernel-only copy of the lock handles and use it for the grants.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Pass the message payload to the component IDC message handlers as an
argument, instead of fetching it from the per-core IDC payload area,
which is kernel memory.

Add idc_cmd_user() to let user-space threads (with CONFIG_SOF_USERSPACE_LL)
process component messages, sent from another core, with a payload
in user-accessible memory. Only the messages, which can originate from
the user-space IPC and LL threads, are supported.

LL modules on a core different from their pipeline core aren't
supported with user-space LL, so fail the prepare instead of trying
to set up an LL task for them.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from 6d2890d to 37c1431 Compare November 9, 2026 12:30
@kv2019i

kv2019i commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

V5 pushed:

  • rebase only, no functional changes

@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from 37c1431 to 5114999 Compare November 9, 2026 15:22

@kv2019i kv2019i left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V6 pushed:

  • addressing remaining code review comments

Comment thread src/ipc/ipc-common.c Resolved
Comment thread src/ipc/ipc-helper.c Resolved
Comment thread src/ipc/ipc-common.c Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The unguarded reference to idc_cmd_user() causes an undefined symbol in non-SMP LL-userspace builds because its definition is compiled only with CONFIG_SMP enabled.

1 open issue
3 resolved since last review

Review effort: Balanced

Comment thread src/ipc/ipc-common.c Resolved
With CONFIG_SOF_USERSPACE_LL, component operations on a module on
another core (cross-core bind, remote get_attribute, free, params,
prepare, trigger, reset) are called from user-space IPC and LL threads.
They use idc_send_msg(), which relies on kernel objects and data and
faults in user mode.

Add a cross-core request slot per core to struct ipc_user. When called
in user context, idc_send_msg() now copies the message and its payload
to the slot of the target core and wakes the user-space IPC thread of
that core, which processes it with idc_cmd_user() and signals
completion. The IPC4 bind / unbind message, referenced by the bind
payload, usually is on the sender's stack, so it's copied as well.

The user-space IPC thread of a core now has two independent producers,
the kernel IPC handler (IPC_USER_EVENT_CMD) and user threads on other
cores (IPC_USER_EVENT_XCORE). Post the events with k_event_post()
instead of k_event_set(), which would clear a pending request of the
other producer.

An LL thread holds the LL scheduler lock of its core for the whole LL
cycle, also when it calls a remote module, e.g. from a timer-driven
pipeline trigger task or when a failed copy resets the pipeline. Waiting
for the target IPC thread with the lock held could deadlock, if that
thread needs the lock, e.g. for a bind locking both cores, or needs the
LL lock of its own core, whose LL thread may in turn wait for a request
to this core. Release all holds of an LL thread on its LL lock while it
waits for a cross-core request, and re-take them afterwards, like the
kernel-mode IDC path does implicitly. Track the LL lock owner and depth
also without CONFIG_ASSERT for this. Like with IDC, the pipeline graph
isn't protected meanwhile: the host resets a pipeline, which waits for
the LL thread, before unbinding or deleting its modules.

An IPC thread, which sends requests while holding LL locks (e.g. the
remote part of a bind), could still deadlock with a request from an LL
thread, in flight to the same core, whose handler needs one of the
locks. Add ipc_user_xcore_reserve() / ipc_user_xcore_release() for
reserving the request slots before taking the locks. A slot, reserved
by the calling thread, is used by ipc_user_xcore_send() directly.

Grant all user-space IPC and LL threads access to the request slots
and LL scheduler locks of all initialized cores. The grants are done
from kernel-only copies of the thread pointers, so keep one for the LL
audio threads as well.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
comp_ipc4_get_attribute_remote() allocates the buffer for the remote
core to fill with rzalloc(), which takes a spinlock and fails in user
mode. With CONFIG_SOF_USERSPACE_LL it's called from user-space threads
and the buffer is filled by the user-space IPC thread of the remote
core, so allocate it from the coherent LL user heap instead.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
With CONFIG_SOF_USERSPACE_LL, ipc_pipeline_module_free() takes the LL
scheduler lock of the core of the first module in the pipeline. That
is supposed to be the pipeline core, but a module can be on another
core (e.g. an offloaded DP module). Freeing such a remote module is
done on its core, where ipc_comp_free() takes the LL lock of that core,
which would deadlock, if it's the lock taken here.

Pass the pipeline core to ipc_pipeline_module_free() and lock that.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Cross-core binding was rejected at build time with
CONFIG_SOF_USERSPACE_LL. Implement the LL blocking for it:

The user-space LL thread holds its scheduler lock for the whole LL
cycle, so taking the LL lock of a core both blocks its next cycle and
waits for the current one to finish. ll_block() / ll_unblock() now take
the two modules and lock the cores of both modules and of their
pipelines, in ascending order to avoid deadlocks. The remote bind /
unbind requests are sent with the locks held, so ll_block() first
reserves the cross-core request slots of the other cores.
ll_wait_finished_on_core() doesn't need to send an IDC anymore.

pipeline_connect() / pipeline_disconnect() asserted the LL lock of the
buffer core. Cross-core buffers always belong to the primary core,
which isn't locked for e.g. a bind between cores 1 and 2, nor when
ipc_pipeline_module_free() disconnects a DP module on core 0 in a
pipeline on core 1. The modified lists are walked by the LL thread of
the pipeline core, so assert the lock of that core instead.

struct pipeline is in cached memory of the pipeline core, so its core
field can be stale on other cores, e.g. on the primary core, which
processes cross-core binds. Record the pipeline core in the coherent
comp_dev, in the new ipc_config.ppl_core, and read it with
comp_ppl_core().

The remote parts of the bind (get_attribute, bind, unbind) go through
idc_send_msg(), which forwards them to the user-space IPC thread of
the remote core.

The domain block / unblock in zephyr_domain.c uses a mutex and a
condition variable, embedded in user memory, and isn't used with
user-space LL, so don't build it then.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
With CONFIG_CROSS_CORE_STREAM and CONFIG_SOF_USERSPACE_LL, a DP module
on a different core than its pipeline can now be bound and operated
across cores. With user-space LL, keep rejecting only LL modules on
another core than their pipeline. Such an LL module would need its own
LL task on that core, which is only set up by the kernel-mode IDC
handlers.

comp_buffer_connect() marked a component shared only when the buffer
is on another core. Cross-core buffers always belong to the primary
core, so e.g. a DP module on core 0 in a pipeline on core 1 was never
marked shared, and its operations, called on the pipeline core, were
run there instead of on core 0. Also mark a component shared, if it's
on another core than its pipeline.

The buffers to such a DP module must be shared even for a bind between
modules on the same core: the LL thread of the pipeline core copies the
data between the buffer and the queue of the DP module. Mark the buffers
shared, if the cores of the modules and of their pipelines differ. The
buffer core still depends only on whether the bind is cross-core.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Cross-core streams are now supported with user-space LL. Stop
disabling CONFIG_CROSS_CORE_STREAM in the LL user-space overlay.

This allows e.g. the compressed playback topology, which runs the
decoder (DP) on core 1, bound to the host copier on core 0.

The overlay is used by all ACE3.x and newer targets (e.g. PTL, WCL,
NVL) built with os_linux_overlay.conf. Tested on PTL.

Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
@kv2019i
kv2019i force-pushed the 202611-lluser-cross-core-ipc-idc branch from 5114999 to f63b614 Compare November 10, 2026 15:59
@kv2019i

kv2019i commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

V7 pushed:

  • one fix to issue raised in code review

@kv2019i

kv2019i commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

@lrudyX can you check? I see MTL DUT failing to "Connection timed out". Rest of DUTs are passing and this PR should have no impact on the MTL runs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants